package com.gitee.hermer.engine.jars.core.context.impl;

import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.data.annotation.Transient;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.gitee.hermer.boot.jee.commons.collection.CollectionUtils;
import com.gitee.hermer.boot.jee.commons.collection.StringCache;
import com.gitee.hermer.boot.jee.commons.exception.ErrorCode;
import com.gitee.hermer.boot.jee.commons.log.UtilsContext;
import com.gitee.hermer.boot.jee.commons.reflect.ReflectUtils;
import com.gitee.hermer.boot.jee.commons.utils.StringUtils;
import com.gitee.hermer.boot.jee.commons.verify.Assert;
import com.gitee.hermer.engine.jars.core.context.IEngineAppContext;
import com.gitee.hermer.engine.jars.core.context.domain.EngineAppInfoContext;
import com.gitee.hermer.engine.jars.core.context.envs.IEngineAppContextEnvironment;
import com.gitee.hermer.engine.jars.core.context.ex.EngineAppIllegalLoaderStateException;
import com.gitee.hermer.engine.jars.core.domain.EngineConstant;
import com.gitee.hermer.engine.jars.dependent.dsm.domain.EngineProperties;

@Component
public class EngineAppContextImpl extends UtilsContext implements IEngineAppContext,IEngineAppContextEnvironment,ApplicationContextAware{

	public EngineAppContextImpl(){
	}

	private static Map<String, EngineAppLoaderState> cacheAppLoaderState = null;

	private static Map<String, EngineAppInfoContext> appsContext = null;


	private ConfigurableApplicationContext applicationContext;

	@Transient
	@JsonIgnore
	private final Map<String, Object> ENVIRONMENT = new ConcurrentHashMap<String, Object>(); 

	public enum EngineAppLoaderState{
		UPDATE, // 文件更新
		BEGIN_INIT,// 初始化之前
		INIT_ING, //初始化中
		END_INIT, // 初始化完毕
		RUN_ING, //运行中
		BEGIN_DESTROY, // 销毁前
		END_DESTROY, // 销毁后
		DEFAULT,
		DISABLED
	}

	@Override
	public boolean updateEngineAppLoaderState(String cacheKey,EngineAppLoaderState state,boolean isVerify){
		if(isVerify){
			EngineAppLoaderState old = cacheAppLoaderState.get(cacheKey);
			if(EngineAppLoaderState.BEGIN_INIT == state && old != null){
				throw new EngineAppIllegalLoaderStateException(String.format("容器[%s]状态非法 -> 启动前请先释放！下个状态[%s] - 想变更为[%s]",cacheKey,findNextState(cacheKey),state));
			}else if(EngineAppLoaderState.END_DESTROY == state){
				cacheAppLoaderState.remove(cacheKey);
			}else if(EngineAppLoaderState.DISABLED == state && old == EngineAppLoaderState.RUN_ING){
				// 状态迁移 -> RUN_ING -> DISABLED
				cacheAppLoaderState.replace(cacheKey, state);
			}else if(EngineAppLoaderState.RUN_ING == state && old == EngineAppLoaderState.DISABLED){
				// 状态迁移 -> DISABLED -> RUN_ING
				cacheAppLoaderState.replace(cacheKey, state);
			}else{
				if(findNextState(cacheKey) == state && cacheAppLoaderState.containsKey(cacheKey)){
					cacheAppLoaderState.replace(cacheKey, state);
				}else if(!cacheAppLoaderState.containsKey(cacheKey)){
					cacheAppLoaderState.put(cacheKey, state);
				}else{
					error("容器[%s]状态警告！下个状态[%s] - 想变更为[%s]",cacheKey,findNextState(cacheKey),state);
					return false;
				}
			}
			info("容器[%s]状态变更 - 变更前[%s] - 变更后[%s]",cacheKey,old,state);
			return true;
		}else{
			info("容器[%s]状态变更 - 变更前[%s] - 变更后[%s]",cacheKey,getCurrentLoaderState(cacheKey),state);
			if(cacheAppLoaderState.containsKey(cacheKey))
				cacheAppLoaderState.replace(cacheKey, state);
			else
				cacheAppLoaderState.put(cacheKey, state);
			return true;
		}
	}



	@Override
	public boolean updateEngineAppLoaderState(String cacheKey,EngineAppLoaderState state){
		return updateEngineAppLoaderState(cacheKey,state,true);
	}
	@Override
	public EngineAppLoaderState getCurrentLoaderState(String cacheKey){
		return cacheAppLoaderState.get(cacheKey);
	}

	private EngineAppLoaderState findNextState(String cacheKey){
		try{
			for (int i = 0; i < EngineAppLoaderState.values().length; i++) {
				if(EngineAppLoaderState.values()[i] == cacheAppLoaderState.get(cacheKey)){
					return EngineAppLoaderState.values()[i+1];
				}
			}
		}catch(Exception e){
		}
		return null;
	}
	

	@Override
	public void afterPropertiesSet() throws Exception {
		cacheAppLoaderState = new ConcurrentHashMap<String, EngineAppLoaderState>();
		appsContext = new ConcurrentHashMap<String, EngineAppInfoContext>();

		applicationContext.addApplicationListener(new ApplicationListener<ContextRefreshedEvent>() {
			
			private final Pattern pattern = Pattern.compile("[\\.a-z]{1,}");
			
			@Override
			public void onApplicationEvent(ContextRefreshedEvent event) {
				Map<String, EngineProperties> beans = event.getApplicationContext().getBeansOfType(EngineProperties.class);
				if(beans != null && !beans.isEmpty()){
					EngineProperties[] engineProperties = beans.values().toArray(new EngineProperties[]{});
					if(engineProperties != null){
						for (EngineProperties properties : engineProperties) {
							Field[] fields = properties.getClass().getDeclaredFields();
							for (Field field : fields) {
								AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(field, Value.class);
								if(attributes != null && StringUtils.isNotEmpty(attributes.getString(EngineConstant.ANNOTATE_FIELD_NAME_VALUE))){
									try{
										String key = attributes.getString(EngineConstant.ANNOTATE_FIELD_NAME_VALUE);
										Matcher matcher = pattern.matcher(key);
										Assert.isTrue(matcher.find());
										Object val = ReflectUtils.invoke(properties, null, properties.getClass().getDeclaredMethod(StringUtils.genGetter(field.getName())));
										ENVIRONMENT.put(matcher.group(), val == null?"":val);
									}catch (Throwable e) {
										error(e.getMessage(),e);
									}
								}
							}
						}
					}
				}
			}
		});
	}


	@Override
	public void destroy() throws Exception {
		if(cacheAppLoaderState != null){
			cacheAppLoaderState.clear();
			cacheAppLoaderState = null;
		}
		if(appsContext != null)
		{
			appsContext.clear();
			appsContext = null;
		}
	}


	@Override
	public void register(String cacheKey, EngineAppInfoContext context) {
		Assert.isTrue(!appsContext.containsKey(cacheKey));
		appsContext.put(cacheKey, context);
	}
	@Override
	public void unregister(String cacheKey){
		if(appsContext.containsKey(cacheKey)){
			appsContext.remove(cacheKey);
		}
	}



	@Override
	public EngineAppInfoContext getEnableAppModelContext(String cacheKey) {
		EngineAppInfoContext appContext = appsContext.get(cacheKey);
		Assert.notNullCode(appContext, ErrorCode.SYSTEM_ERROR,String.format("容器[%s]已释放或者不存在！...", cacheKey));
		EngineAppLoaderState state = appContext.getLoaderState();
		Assert.isTrueCode(state == EngineAppLoaderState.RUN_ING,ErrorCode.SYSTEM_ERROR,String.format("容器[%s]状态错误[%s]！...", cacheKey,state));
		return appContext;
	}



	@Override
	public EngineAppInfoContext getEnableAppModelContext(String projectName, String version) {
		return getEnableAppModelContext(new StringCache(projectName).append(EngineConstant.STR_SPLIT).append(version).toString());
	}



	@Override
	public List<EngineAppInfoContext> getEnableAppModelContextList() {
		return CollectionUtils.newArrayList(appsContext.values());
	}



	@Override
	public boolean containsProperty(String key) {
		return ENVIRONMENT.containsKey(key);
	}



	@Override
	public String getProperty(String key) {
		return getProperty(key, String.class);
	}

	@Override
	public String getProperty(String key, String defaultValue) {
		return getProperty(key, String.class, defaultValue);
	}



	@Override
	public <T> T getProperty(String key, Class<T> targetType) {
		return (T)ENVIRONMENT.get(key);
	}



	@Override
	public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
		return (T)ENVIRONMENT.getOrDefault(key, defaultValue);
	}



	@Override
	@Deprecated
	public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
		throw new UnsupportedOperationException();
	}



	@Override
	public String getRequiredProperty(String key) throws IllegalStateException {
		Assert.hasText(getProperty(key));
		return getProperty(key);
	}



	@Override
	public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException {
		Assert.notNull(getProperty(key, targetType));
		return getProperty(key, targetType);
	}



	@Override
	@Deprecated
	public String resolvePlaceholders(String text) {
		throw new UnsupportedOperationException();
	}



	@Override
	@Deprecated
	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
		throw new UnsupportedOperationException();
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = (ConfigurableApplicationContext) applicationContext;
	}



	@Override
	public Map<String, Object> getEngineAppContextEnvironment() {
		return ENVIRONMENT;
	}

}
